#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2024/4/8 17:07
# @Author  : 王凯
# @File    : pdd_verify.py
# @Project : scrapy_spider
import base64
import json
import random
import re
import time

import cv2
import numpy as np
import parsel
import requests
import rsa
from loguru import logger

from apps.pdd.pdd.utils.js import get_prepare_token, gen_anti_content, encrypt_params, deal_encrypt_salt, decrypt_response, img_decode
from utils.proxies_tools import get_company_ip_crawler_by_api


class PddSliderVerify:
    session = requests.Session()
    code_msg = {
        "1000": "验证时间过长，请重试",
        "1001": "验证时间过长，请重试",
        "1002": "系统繁忙，请稍后重试",
        "1004": "验证频繁，请稍后重试",
        "3000": "系统繁忙，请稍后重试",
        "4000": "网络异常，请重试",
        "": "网络异常",
        "0": "调用成功",
    }

    def __init__(self, verify_auth_token=None):
        # self.base_url = "https://mobile.pinduoduo.com"
        self.base_url = "https://m.pinduoduo.net"
        self.random_aes_iv = None
        self.random_aes_key = None
        self.aes_iv = None
        self.aes_key = None
        self.verify_auth_token = verify_auth_token

    def init(self):
        self.session.proxies = get_company_ip_crawler_by_api()
        logger.debug(f"设置代理 {self.session.proxies}")
        self.session.headers = {
            "User-Agent": f"Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/{random.randint(600, 700)}.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
        }
        url = f"{self.base_url}/psnl_verification.html?VerifyAuthToken={self.verify_auth_token}"
        logger.info(f"请求验证码页面: {url}")
        html = self.session.get(url, timeout=5).text
        js_url = parsel.Selector(html).xpath('//script[contains(@src, "psnl_verification")]/@src').get()
        js_url = js_url or parsel.Selector(html).xpath('//link[contains(@href, "psnl_verification") and contains(@href, ".js")]/@href').get()
        logger.debug(f"请求验证码js: {js_url}")
        js_code = self.session.get(js_url, timeout=5).text
        key_iv = re.findall(r"=\"([^\"]*?)\",\w=\"([^\"]*?)\".{1,200}{a:\w\(16\),v:\w\(16\)}", js_code)
        if key_iv:
            self.aes_key, self.aes_iv = key_iv[0]
        else:
            raise Exception("获取aes_key,aes_iv失败")

        self.random_aes_key = self.gen_random_str(16)
        self.random_aes_iv = self.gen_random_str(16)

    def encrypt_b64(self, text=None):
        public_key = "00B5F32E7F62AC544E16C185366CC4D743A28C6D23D94648862530A1A984090ED6B59C6C8F04C4874245F26C9693C8085AE5E36A411D29D769C2210FFEDFE330254812D88C559E9618AE945FD4AD197FE8E4C148B439F3C277E78B38B90058759C8EAC3E67C7A6EB4B3B76650971BCA6B6E41FC74437A4012ECE410D8B0AD194C1B5CDD8BED891670274A4F88AAA4438361A384A5CA89EA6DBAB27E7934A5B82EFD5854D114803195572EAC50908C9335A523B81AD0C5AFA8E50C626B403965F2622CF38BDD2246A807B552D7344D6EE1A6E30A4E3B3A4A94C6CA76C3A96782639521FF91D3C0C076150F117DEE1E36E0A35E8A50AC1A465686DAD1AB7078592C1"
        public_key_mode = "010001"
        bytes_data = rsa.encrypt(text.encode("utf-8"), rsa.PublicKey(int(public_key, 16), int(public_key_mode, 16)))
        new_text = base64.b64encode(bytes_data).decode("utf-8")
        return new_text

    @staticmethod
    def gen_random_str(bit=16):
        base_str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
        return "".join(random.choices(base_str, k=bit))

    def get_salt(self):
        url = f"{self.base_url}/proxy/api/api/phantom/vc_pre_ck?pdduid=0"
        anti_content = gen_anti_content()

        data = {
            "a": self.encrypt_b64(self.random_aes_key),
            "v": self.encrypt_b64(self.random_aes_iv),
            "e": True,
            "verify_auth_token": self.verify_auth_token,
            "content": encrypt_params(
                {
                    "anti_content": anti_content,
                    "sdk_type": 1,
                    "client_time": int(time.time() * 1000),
                },
                key=self.random_aes_key,
                iv=self.random_aes_iv,
            ),
        }
        data = json.dumps(data, separators=(",", ":"))
        headers = {
            **self.session.headers,
            "Content-Type": "application/json;charset=UTF-8",
        }
        response = self.session.post(url, data=data, headers=headers).json()
        salt = response.get("salt")
        servertime = response["server_time"]
        return salt, servertime

    def get_image(self, servertime, salt, salt_aes_key, salt_aes_iv):
        url = f"{self.base_url}/proxy/api/api/phantom/obtain_captcha?pdduid=0"
        data = {
            "e": True,
            "verify_auth_token": self.verify_auth_token,
            "content": encrypt_params(
                {
                    "anti_content": gen_anti_content(),
                    "captcha_collect": get_prepare_token(
                        {
                            "v": "b",
                            "ts": servertime,
                            "t0": int(time.time() * 1000),
                            "tp": 1,
                            "ua": self.session.headers["User-Agent"],
                            "rf": f"{self.base_url}/login.html",
                            "hl": "000000001001010",
                            "sc": {"w": 400, "h": 700},
                            "ihs": 1,
                            "platform": 3,
                        },
                        key=salt_aes_key,
                        iv=salt_aes_iv,
                    ),
                    "useNativeAntiToken": True,
                    "salt": salt,
                },
                key=self.random_aes_key,
                iv=self.random_aes_iv,
            ),
        }
        response = self.session.post(url, json=data).json()
        resp = decrypt_response(response, self.random_aes_key, self.random_aes_iv)
        resp.update({"msg": self.code_msg.get(str(str(response["code"])))})
        return resp

    def gen_track(self, distance):
        # 不校验
        def __ease_out_expo(sep):
            """
            轨迹相关操作
            """
            if sep == 1:
                return 1
            else:
                return 1 - pow(2, -10 * sep)

        if not isinstance(distance, int) or distance < 0:
            raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
        # 初始化轨迹列表
        slide_track = []
        # 共记录count次滑块位置信息
        count = 20
        # 初始化滑动时间
        t = int(time.time() * 1000) - 4000
        # 记录上一次滑动的距离
        _x = 0
        _y = 0
        for i in range(count):
            # 已滑动的横向距离
            x = round(__ease_out_expo(i / count) * distance)
            # y = round(__ease_out_expo(i / count) * 14)
            # 滑动过程消耗的时间
            t += random.randint(10, 50)
            if x == _x:
                continue
            slide_track.append([x, random.randint(170, 180) + 0.5, t, 11.5, 11.5, 0, 1])
            _x = x
        slide_track.append(slide_track[-1])
        return slide_track

    def verify_image(self, distance, servertime, salt, salt_aes_key, salt_aes_iv):
        url = f"{self.base_url}/proxy/api/api/phantom/user_verify?pdduid=0"
        track = self.gen_track(distance)
        data = {
            "e": True,
            "verify_auth_token": self.verify_auth_token,
            "content": encrypt_params(
                {
                    "anti_content": gen_anti_content(),
                    "useNativeAntiToken": True,
                    "salt": salt,
                    "captcha_collect": get_prepare_token(
                        {
                            "v": "b",
                            "ts": servertime,
                            "t1": int(time.time() * 1000),
                            "t2": track[-1][2],
                            "tp": 1,
                            "ua": self.session.headers["User-Agent"],
                            "rf": f"{self.base_url}/login.html",
                            "platform": 3,
                            "hl": "000000001001010",
                            "sc": {"w": 400, "h": 700},
                            "ihs": 1,
                            "imageSize": {"width": 267, "height": 133},
                            "del": track[:1],
                            "mel": track,
                            "uel": track[-1:],
                            "mell": [],
                            "de": [
                                [{"class": "QkGS9nId", "id": None}],
                                [{"class": "QkGS9nId", "id": None}],
                                [{"class": "QkGS9nId", "id": None}],
                                [{"class": "slide-ani", "id": None}],
                            ],
                            "isb": 1,
                        },
                        key=salt_aes_key,
                        iv=salt_aes_iv,
                    ),
                    "verify_code": int(distance),
                },
                key=self.random_aes_key,
                iv=self.random_aes_iv,
            ),
        }
        ret = self.session.post(url, json=data).json()
        msg = f"验证结果：{ret}"
        if ret.get("code") == 0:
            if ret.get("result") is True:
                logger.success(msg)
                return True
            else:
                logger.error(msg)
                return False
        else:
            logger.error(msg)
            return False

    def get_image_distance(self, target_bytes, background_bytes):
        target = cv2.imdecode(np.frombuffer(target_bytes, np.uint8), cv2.IMREAD_ANYCOLOR)
        background = cv2.imdecode(np.frombuffer(background_bytes, np.uint8), cv2.IMREAD_ANYCOLOR)
        background = cv2.GaussianBlur(background, (5, 5), 1)
        _, background = cv2.threshold(background, 110, 255, cv2.THRESH_BINARY)
        _, target = cv2.threshold(target, 20, 255, cv2.THRESH_BINARY)
        target = cv2.bitwise_not(target)
        res = cv2.matchTemplate(background, target, cv2.TM_CCOEFF_NORMED)
        min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
        return int(max_loc[0])

    def run_one(self):
        logger.info(f"{'*' * 50} PDD 验证开始 {'*' * 50}")
        self.init()
        salt, servertime = self.get_salt()
        if not salt:
            raise Exception(f"{self.verify_auth_token} 已过期")
        salt_aes = deal_encrypt_salt(salt, self.aes_key, self.aes_iv)
        salt_aes_key = salt_aes["aes_key"]
        salt_aes_iv = salt_aes["aes_iv"]
        logger.debug(f"salt: {salt}, servertime: {servertime} salt_aes_key: {salt_aes_key}, salt_aes_iv: {salt_aes_iv}")
        for _ in range(3):
            image_info = self.get_image(servertime, salt, salt_aes_key, salt_aes_iv)
            logger.info(f"verify_type: {image_info.get('type')} msg: {image_info.get('msg')}")
            if image_info.get("msg") == "验证频繁，请稍后重试":
                return False
            pictures = image_info.get("pictures")
            bg1 = base64.b64decode(img_decode(pictures[0]).replace("data:image/png;base64,", ""))
            bg2 = base64.b64decode(img_decode(pictures[1]).replace("data:image/png;base64,", ""))
            distance = self.get_image_distance(target_bytes=bg2, background_bytes=bg1)
            new_distance = int(distance * 267 / 320) + int((60 * (267 / 320)) / 2)
            logger.debug(f"distance: before: {distance}, after: {new_distance}")
            verify_result = self.verify_image(new_distance, servertime, salt, salt_aes_key, salt_aes_iv)
            if verify_result is False:
                continue
            logger.info(f"{'*' * 50} PDD 验证结束 {'*' * 50}")
            return verify_result

        raise Exception("滑块验证失败")

    def run(self):
        for i in range(3):
            try:
                return self.run_one()
            except Exception as e:
                logger.error(e)
                continue
        return False


if __name__ == "__main__":
    test = PddSliderVerify("hLZmbfGauQVdSByC0h8KNg3a79259436208e023")
    test.run()
    # test.gen_track(103)
